package com.elitel.common.businessJdbc.mongodb;

import java.util.*;
import java.util.regex.Pattern;

import com.elitel.common.businessJdbc.BusinessSelectPreparedStatement;
import com.elitel.common.businessJdbc.BusinessSelectPreparedStatementHandler;
import com.elitel.common.mongodb.MongoDBUtil;
import com.elitel.common.mongodb.QueryInfo;
import com.elitel.common.utils.LogTraceUtil;
import com.elitel.common.utils.XCommonUtils;
import com.elitel.common.utils.LogTraceUtil.LogTraceTask;
import com.elitel.common.utils.LogTraceUtil.LogTracer;
import com.github.vincentrussell.query.mongodb.sql.converter.MongoDBQueryHolder;
import com.github.vincentrussell.query.mongodb.sql.converter.QueryConverter;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;

import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.springframework.stereotype.Component;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;

// here is connect mongo client to mongo server
// order general api reuse code
@Component
@Slf4j
public class MongoSelectStatementHandler extends BusinessSelectPreparedStatementHandler{
    // here is handler delegate for mongodUtil

    @Override
    public List<Map<String, Object>> findRowListOfFieldResultByExecuteSql(BusinessSelectPreparedStatement businessPreparedStatement) {
        LogTracer logTracer = LogTraceUtil.getLogTracer();
        LogTraceTask logTraceTask = logTracer.recordTask("ready mongo connect and precompileSql ");

        MongoDBUtil instance = MongoDBUtil.getInstance();
        QueryInfo queryInfo = instance.sql2MongoQuery(businessPreparedStatement.getPrecompileSql());
        MongoDatabase db = instance.createConnection(businessPreparedStatement.getCfgDb().getDbUrl());
        logTraceTask.endTask();

        queryInfo.setDb(db);
        queryInfo.setWhere(handleWhereConditionField(businessPreparedStatement));
        queryInfo.setPageable(businessPreparedStatement.isPageable());
        //处理 MongoDB分页
        queryInfo.setPageSize(businessPreparedStatement.getPageSize());
        queryInfo.setSkipPageNo(queryInfo.getPageSize() * (businessPreparedStatement.getPageNumber() - 1));
        // hrere is build log
        MongoRecord record = new MongoRecord();
        record.setServiceGuid(businessPreparedStatement.getServiceGuid());
        record.setPrecompileSql(businessPreparedStatement.getPrecompileSql());
        record.setDb(db);
        record.setCollectionName(queryInfo.getCollectionName());
        record.setFields(queryInfo.getFields());
        record.setWhereFields(queryInfo.getWhere());
        log.info(record.formatInfo());

        logTraceTask = logTracer.recordTask("execute query to mongodb");
        List<Map<String, Object>> all = instance.findAll(queryInfo);
        logTraceTask.setImportContent("current find query count in condition = " + all.size());
        logTraceTask.endTask();
        return all;

    }

    @Override
    public Long rowCountByExecuteSql(BusinessSelectPreparedStatement businessPreparedStatement) {
        LogTracer logTracer = LogTraceUtil.getLogTracer();
        LogTraceTask task = logTracer.recordTask("mongo connect , precompileSql ,find total data count ");

        MongoDBUtil instance = MongoDBUtil.getInstance();
        QueryInfo queryInfo = instance.sql2MongoQuery(businessPreparedStatement.getPrecompileSql());
        MongoDatabase db = instance.createConnection(businessPreparedStatement.getCfgDb().getDbUrl());
        queryInfo.setDb(db);
        queryInfo.setWhere(handleWhereConditionField(businessPreparedStatement));
        Long count = instance.findCount(queryInfo);

//        List<Map<String, Object>> all = instance.findAll(queryInfo);
        task.setImportContent("total count in condition = " + count);
        task.endTask();

        return count;
    }

    private BasicDBObject handleWhereConditionField(BusinessSelectPreparedStatement businessPrepareStatement) {
        BasicDBObject whereField = new BasicDBObject();
        List<BasicDBObject> conditionElementList = new LinkedList<>();
        for(Map.Entry<String,Object> conditionEntry : businessPrepareStatement.getConditionSection().entrySet()){
            String columnName = conditionEntry.getKey();
            Object columnValue = conditionEntry.getValue();
            String columnValueType = businessPrepareStatement.getFieldTypeMap().get(columnName);
            String conditionSymbol = businessPrepareStatement.getFieldConditionSymbol().get(columnName);

            //https://howtodoinjava.com/mongodb/mongodb-find-documents/
            BasicDBObject conditionElement = new BasicDBObject();
            if("_id".equals(columnName)){
                conditionElement.put("_id", new ObjectId(columnValue.toString()));
            }else{
                switch(conditionSymbol){
                    case " = ":
//                        https://stackoverflow.com/questions/2201925/converting-iso-8601-compliant-string-to-java-util-date
//                        Calendar calendar = javax.xml.bind.DatatypeConverter.parseDateTime("2022-09-28T02:39:36.992Z");
                        conditionElement.put(columnName, handleDataType(columnValue, columnValueType));
                        break;
                    case " != ":
                        conditionElement.put(columnName, new BasicDBObject("$ne", handleDataType(columnValue, columnValueType)));
                        break;
                    case " > ":
                        conditionElement.put(columnName, new BasicDBObject("$gt", handleDataType(columnValue, columnValueType)));
                        break;
                    case " >= ":
                        conditionElement.put(columnName, new BasicDBObject("$gte",handleDataType(columnValue, columnValueType)));
                        break;
                    case " < ":
                        conditionElement.put(columnName, new BasicDBObject("$lt", handleDataType(columnValue, columnValueType)));
                        break;
                    case " <= ":
                        conditionElement.put(columnName, new BasicDBObject("$lte",handleDataType(columnValue, columnValueType)));
                        break;
                    case " like ":
                        String value = columnValue.toString().replaceAll("'%", ".*").replaceAll("%'", ".*");
                        Pattern pattern = Pattern.compile(value, Pattern.CASE_INSENSITIVE);
                        conditionElement.put(columnName, new Document("$regex", pattern));
                        break;
                    case " in ":
                        if (!ObjectUtils.isEmpty(columnValue)) {
                            if(columnValue instanceof String){
                                BasicDBList list = new BasicDBList();
                                String[] columnValueArray = columnValue.toString().split(",");
                                Arrays.stream(columnValueArray).forEach(item -> {
                                    if (!ObjectUtils.isEmpty(item)) {
                                        list.add(handleDataType(item, columnValueType));
                                    }
                                });
                                if (list.size() > 0) {
                                    conditionElement.put(columnName, new BasicDBObject("$in", list));
                                }
                            }
                        }
                        break;
                    default:
                        conditionElement.put(columnName, columnValue);
                        break;
                }
            }
            conditionElementList.add(conditionElement);
        }
        if(XCommonUtils.isNotEmpty(conditionElementList)){
            whereField.put("$and", conditionElementList);
        }

        return whereField;
    }

    private Object handleDataType(Object columnValue, String columnValueType) {
        Object convertResult = null;

        if(XCommonUtils.isContainLeastOneString(columnValueType, "date")){
            // already handle in BusinessPreparedStatement
            return columnValue;
        }

        if(XCommonUtils.isContainLeastOneString(columnValueType.toLowerCase(), "double")){
            return Double.parseDouble(columnValue.toString());
        }

        if(XCommonUtils.isContainLeastOneString(columnValueType.toLowerCase(), "float")){
            return Float.parseFloat(columnValue.toString());
        }

        if(XCommonUtils.isContainLeastOneString(columnValueType, "long", "int")){
            try{
                convertResult = Long.parseLong(columnValue.toString());
            }catch(Exception e){
                convertResult = columnValue;
            }
        }

        return convertResult == null ? columnValue : convertResult;
    }

    // here is create MongoRecord
    // record service ID
    // record origin sql of precompile
    // record queryInfo
    // collectionName
    // fields of basicDBObject
    // where fields of basicDBObject
    @Data
    public static class MongoRecord{
        private String serviceGuid;
        private String precompileSql;
        private MongoDatabase db;
        private String collectionName;
        private BasicDBObject fields;
        private BasicDBObject whereFields;

        public String formatInfo() {
            StringBuilder sb = new StringBuilder();
            sb.append("\r\n===========> \r\n serviceGuid (").append(serviceGuid).append(")")
                .append("\r\n precompileSql: ").append(precompileSql)
                .append("\r\n mongoDatabase: ").append(db)
                .append("\r\n collectionName: ").append(collectionName)
                .append("\r\n fields: \t").append(fields)
                .append("\r\n whereFields: \t").append(whereFields)
                .append("\r\n<===========");

            return sb.toString();
        }


    }

}
